package com.yupi.PrepZone.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.PrepZone.common.ErrorCode;
import com.yupi.PrepZone.constant.CommonConstant;
import com.yupi.PrepZone.exception.BusinessException;
import com.yupi.PrepZone.exception.ThrowUtils;
import com.yupi.PrepZone.mapper.QuestionBankQuestionMapper;
import com.yupi.PrepZone.model.dto.questionBankQuestion.QuestionBankQuestionBatchRemoveRequest;
import com.yupi.PrepZone.model.dto.questionBankQuestion.QuestionBankQuestionQueryRequest;
import com.yupi.PrepZone.model.entity.Question;
import com.yupi.PrepZone.model.entity.QuestionBank;
import com.yupi.PrepZone.model.entity.QuestionBankQuestion;
import com.yupi.PrepZone.model.entity.User;
import com.yupi.PrepZone.model.vo.QuestionBankQuestionVO;
import com.yupi.PrepZone.model.vo.UserVO;
import com.yupi.PrepZone.service.QuestionBankQuestionService;
import com.yupi.PrepZone.service.QuestionBankService;
import com.yupi.PrepZone.service.QuestionService;
import com.yupi.PrepZone.service.UserService;
import com.yupi.PrepZone.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 题库题目关联服务实现
 *
 * @author <a href="https://github.com/liyupi">程序员鱼皮</a>
 * @from <a href="https://www.code-nav.cn">编程导航学习圈</a>
 */
@Service
@Slf4j
public class QuestionBankQuestionServiceImpl extends ServiceImpl<QuestionBankQuestionMapper, QuestionBankQuestion> implements QuestionBankQuestionService {

    @Resource
    private UserService userService;

    @Resource
    private QuestionBankService  questionBankService;

    @Resource
    @Lazy
    private QuestionService questionService;
    @Autowired
    private QuestionBankQuestionMapper questionBankQuestionMapper;

    /**
     * 校验数据
     *
     * @param questionBankQuestion
     * @param add      对创建的数据进行校验
     */
    @Override
    public void validQuestionBankQuestion(QuestionBankQuestion questionBankQuestion, boolean add) {
        ThrowUtils.throwIf(questionBankQuestion == null, ErrorCode.PARAMS_ERROR);
        // todo 从对象中取值
        Long questionBankId = questionBankQuestion.getQuestionBankId();
        Long questionId = questionBankQuestion.getQuestionId();

        QuestionBank bank = questionBankService.getById(questionBankId);
        ThrowUtils.throwIf(bank == null, ErrorCode.PARAMS_ERROR);
        Question question = questionService.getById(questionId);
        ThrowUtils.throwIf(question == null, ErrorCode.PARAMS_ERROR);

        // 创建数据时，参数不能为空
        if (add) {
            // todo 补充校验规则

        }
        // 修改数据时，有参数则校验
        // todo 补充校验规则
//        if (StringUtils.isNotBlank(title)) {
//            ThrowUtils.throwIf(title.length() > 80, ErrorCode.PARAMS_ERROR, "标题过长");
//        }
    }

    /**
     * 获取查询条件
     *
     * @param questionBankQuestionQueryRequest
     * @return
     */
    @Override
    public QueryWrapper<QuestionBankQuestion> getQueryWrapper(QuestionBankQuestionQueryRequest questionBankQuestionQueryRequest) {
        QueryWrapper<QuestionBankQuestion> queryWrapper = new QueryWrapper<>();
        if (questionBankQuestionQueryRequest == null) {
            return queryWrapper;
        }
        // todo 从对象中取值
        Long id = questionBankQuestionQueryRequest.getId();
        Long notId = questionBankQuestionQueryRequest.getNotId();
        String sortField = questionBankQuestionQueryRequest.getSortField();
        String sortOrder = questionBankQuestionQueryRequest.getSortOrder();
        Long questionBankId = questionBankQuestionQueryRequest.getQuestionBankId();
        Long questionId = questionBankQuestionQueryRequest.getQuestionId();
        Long userId = questionBankQuestionQueryRequest.getUserId();
        // todo 补充需要的查询条件
        // 从多字段中搜索
        // 精确查询
        queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionBankId), "questionBankId", questionBankId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionId), "questionId", questionId);
        // 排序规则
        queryWrapper.orderBy(SqlUtils.validSortField(sortField),
                sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 获取题库题目关联封装
     *
     * @param questionBankQuestion
     * @param request
     * @return
     */
    @Override
    public QuestionBankQuestionVO getQuestionBankQuestionVO(QuestionBankQuestion questionBankQuestion, HttpServletRequest request) {
        // 对象转封装类
        QuestionBankQuestionVO questionBankQuestionVO = QuestionBankQuestionVO.objToVo(questionBankQuestion);

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Long userId = questionBankQuestion.getUserId();
        User user = null;
        if (userId != null && userId > 0) {
            user = userService.getById(userId);
        }
        UserVO userVO = userService.getUserVO(user);
        questionBankQuestionVO.setUser(userVO);
        // 2. 已登录，获取用户点赞、收藏状态
        long questionBankQuestionId = questionBankQuestion.getId();
        User loginUser = userService.getLoginUserPermitNull(request);

        // endregion

        return questionBankQuestionVO;
    }

    /**
     * 分页获取题库题目关联封装
     *
     * @param questionBankQuestionPage
     * @param request
     * @return
     */
    @Override
    public Page<QuestionBankQuestionVO> getQuestionBankQuestionVOPage(Page<QuestionBankQuestion> questionBankQuestionPage, HttpServletRequest request) {
        List<QuestionBankQuestion> questionBankQuestionList = questionBankQuestionPage.getRecords();
        Page<QuestionBankQuestionVO> questionBankQuestionVOPage = new Page<>(questionBankQuestionPage.getCurrent(), questionBankQuestionPage.getSize(), questionBankQuestionPage.getTotal());
        if (CollUtil.isEmpty(questionBankQuestionList)) {
            return questionBankQuestionVOPage;
        }
        // 对象列表 => 封装对象列表
        List<QuestionBankQuestionVO> questionBankQuestionVOList = questionBankQuestionList.stream().map(questionBankQuestion -> {
            return QuestionBankQuestionVO.objToVo(questionBankQuestion);
        }).collect(Collectors.toList());

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Set<Long> userIdSet = questionBankQuestionList.stream().map(QuestionBankQuestion::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));

        // endregion

        questionBankQuestionVOPage.setRecords(questionBankQuestionVOList);
        return questionBankQuestionVOPage;
    }
    /*
    * 管理员批量向题库中添加题目
    * */
    @Override
    public void batchAddQuestionsTOBank(List<Long> questionIdList, long questionBankId, User loginUser) {
        //校验参数
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR,"题目列表不能为空");
        ThrowUtils.throwIf(questionBankId <= 0, ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(loginUser == null, ErrorCode.PARAMS_ERROR);
        //检查题目id是否存在
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = Wrappers.lambdaQuery(Question.class)
                .select(Question::getId)
                .in(Question::getId, questionIdList);
        List<Long> vaildIdList = questionService.listObjs(questionLambdaQueryWrapper,obj->(Long) obj);
        //过滤掉关联表中已存在的数据，防止插入失败
        LambdaQueryWrapper lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId)
                .in(QuestionBankQuestion::getQuestionId, vaildIdList);
        List<QuestionBankQuestion> existQuestionList = this.list(lambdaQueryWrapper);
        Set<Long> existQuestionIdSet = existQuestionList.stream().map(QuestionBankQuestion::getId)
                .collect(Collectors.toSet());
        vaildIdList = existQuestionIdSet.stream().filter(questionId -> {
            return !existQuestionIdSet.contains(questionId);
        }).collect(Collectors.toList());

        ThrowUtils.throwIf(CollUtil.isEmpty(vaildIdList), ErrorCode.PARAMS_ERROR,"合法的题目不存在");
        //检查题库id是否存在
        QuestionBank bank = questionBankService.getById(questionBankId);
        ThrowUtils.throwIf(bank == null, ErrorCode.PARAMS_ERROR);
        //自定义线程池
        ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
                4,
                10,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        //创建异步任务队列
        List<CompletableFuture> futures = new ArrayList<>();
        //执行插入
        int batchSize = 1000;
        for (int i = 0; i < vaildIdList.size(); i+=batchSize) {
            List<Long> subList = vaildIdList.subList(i, Math.min(i + batchSize, vaildIdList.size()));
            List<QuestionBankQuestion> questionBankQuestionList = subList.stream()
                    .map(questionId -> {
                        QuestionBankQuestion questionBankQuestion = new QuestionBankQuestion();
                        questionBankQuestion.setQuestionBankId(questionBankId);
                        questionBankQuestion.setQuestionId(questionId);
                        questionBankQuestion.setUserId(loginUser.getId());
                        return questionBankQuestion;
                    }).collect(Collectors.toList());
            //使用事务处理每批数据
            //获取代理对象
            QuestionBankQuestionServiceImpl currentProxy = (QuestionBankQuestionServiceImpl) AopContext.currentProxy();
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                        currentProxy.batchAddQuestionToBank(questionBankQuestionList);
                    }
            , customExecutor).exceptionally(ex -> {
               log.error("批处理任务失败：",ex);
               return null;
            });
            futures.add(future);
        }
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        //关闭线程池
        customExecutor.shutdown();
    }
    //Spring事务是基于代理实现的
    @Transactional(rollbackFor = Exception.class)
    public void batchAddQuestionToBank(List<QuestionBankQuestion> questionBankQuestionList){
        try {
            boolean save = this.saveBatch(questionBankQuestionList);
            ThrowUtils.throwIf(!save, ErrorCode.PARAMS_ERROR);
        }catch (DataIntegrityViolationException e){
            log.error("数据库唯一键冲突");
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }catch (DataAccessException e){
            log.error("数据库连接问题");
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }catch (Exception e){
            log.error("发生未知错误");
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }
    }
    /*
    * 管理员批量向题库中移除题目
    * */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchRemoveQuestionToBank(List<Long> questionIdList, long questionBankId) {
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR,"题目列表不能为空");
        ThrowUtils.throwIf(questionBankId <= 0, ErrorCode.PARAMS_ERROR);
        //校验题目是否存在
        List<Question> questionList = questionService.listByIds(questionIdList);
        List<Long> vaildQuestionId = questionList.stream()
                .map(Question::getId)
                .collect(Collectors.toList());
        ThrowUtils.throwIf(CollUtil.isEmpty(vaildQuestionId), ErrorCode.PARAMS_ERROR);
        //校验题库是否存在
        QuestionBank bank = questionBankService.getById(questionBankId);
        ThrowUtils.throwIf(bank == null, ErrorCode.PARAMS_ERROR);
        for (Long questionId : vaildQuestionId) {
            QueryWrapper queryWrapper = new QueryWrapper();
            queryWrapper.eq(ObjectUtils.isNotEmpty(questionId), "questionId", questionId);
            queryWrapper.eq(ObjectUtils.isNotEmpty(questionBankId), "questionBankId", questionBankId);
            boolean remove = this.remove(queryWrapper);
            ThrowUtils.throwIf(!remove, ErrorCode.PARAMS_ERROR);
        }

    }

}
